package com.mdp.im.core.socket;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.mdp.core.utils.LogUtils;
import com.mdp.im.core.vo.SimpleUser;
import com.mdp.im.core.utils.ImTools;
import com.mdp.core.utils.SequenceFormat;
import com.mdp.im.core.publish.api.service.PublishService;
import com.mdp.im.core.service.ImLoginUtils;
import com.mdp.im.core.utils.msg.Action;
import com.mdp.im.core.utils.msg.MsgType;
import com.mdp.safe.client.entity.User;
import com.mdp.safe.client.jwt.JwtLocalLoginService;
import com.mdp.safe.client.utils.LoginUtils;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.Map.Entry;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Http 处理器
 * 
 * @author chenyc
 * @date 2020/3/29
 */
@Service
@Sharable
public final class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<Object> {
    /**
     * ChannelInboundHandlerAdapter的channelRead方法处理完消息后不会自动释放消息，
     * 若想自动释放收到的消息，可以使用SimpleChannelInboundHandler。
     */

    private WebSocketServerHandshaker handshaker;

    @Autowired
    JwtLocalLoginService jwtLoginService;
    
    @Autowired
    PublishService pushService;


    
     
   

    private static final Logger log = LoggerFactory.getLogger(TextWebSocketFrameHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理HTTP请求
        String gloNo=LogUtils.getGloNo(false);
        if(StringUtils.isEmpty(gloNo)) {
            gloNo=LogUtils.genGloNo();
            log.info(">>> 客户端没有上送 全局跟踪号，将自动生成全局跟踪号： "+LogUtils.GLO_NO_NAME+"【"+gloNo+"】");
        }else {
            log.info(">>> 客户端上送 全局跟踪号： "+LogUtils.GLO_NO_NAME+"【"+gloNo+"】");
        }
        MDC.put("gloNo",gloNo);
        try {

            if (msg instanceof FullHttpRequest) {
                handleHttpRequest(ctx, (FullHttpRequest) msg);
                return;
            }
            if (msg instanceof WebSocketFrame) {
                // 处理WebSocket请求
                handleWebSocketFrame(ctx, (WebSocketFrame) msg);
            }

        }finally {

            MDC.remove("gloNo");
        }
        // 手动释放消息（SimpleChannelInboundHandler会自动释放）
        // ReferenceCountUtil.release(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx,cause);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //this.clientOffline(ctx);
        super.handlerRemoved(ctx);
    }
    
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        this.clientOffline(ctx);
        super.channelInactive(ctx);
    }

    /**
     * 处理HTTP请求
     * 
     * @param ctx
     * @param request
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {

        // WebSocket访问，处理握手升级。
            log.debug(request.uri());
            // Handshake
            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(request), null, true);
            //微信socket需要
            String secWebSocketProtocol=request.headers().get("Sec-WebSocket-Protocol");

            if(!StringUtils.isEmpty(secWebSocketProtocol)) {
                 wsFactory= new WebSocketServerHandshakerFactory(getWebSocketLocation(request), secWebSocketProtocol, true);
            }
            //END 微信socket

            handshaker = wsFactory.newHandshaker(request);
            if (handshaker == null) {
                // 无法处理的WebSocket版本
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
                log.error("无法处理的WebSocket版本");
                return;
            }

            // 验证token
            String token = getRequestParameter(request, "accessToken");
            if (token == null) {
                FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.UNAUTHORIZED,
                        Unpooled.copiedBuffer("Token not found in request url", CharsetUtil.UTF_8));
                ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
                log.error("Token not found in request url 没有accessToken参数");
                return;
            }
            jwtLoginService.doLocalLoginUseJwtToken(token);
            // 向客户端发送WebSocket握手，完成握手。

            User user2= LoginUtils.getCurrentUserInfo();

            String userid =user2.getUserid();
            String username=user2.getUsername();
            log.debug("登录成功--- userid:"+userid+",username:"+username);
            String sex=user2.getSex();
            String headimgurl=user2.getHeadimgurl();
            SimpleUser user=new SimpleUser();
            user.setHeadimgurl(headimgurl);
            user.setUserid(userid);
            user.setSex(sex);
            user.setUsername(username);

            ChannelFuture channelFuture = handshaker.handshake(ctx.channel(), request);
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    try {
                        /**
                         * 将用户注册到分布式缓存中
                         */
                        SimpleUser redisUser = ImLoginUtils.getSimpleUser(user.getUserid());
                        if (redisUser != null) {
                            if (!StringUtils.isEmpty(user.getUsername())) {
                                redisUser.setUsername(user.getUsername());
                            }
                            if (!StringUtils.isEmpty(user.getHeadimgurl())) {
                                redisUser.setHeadimgurl(user.getHeadimgurl());
                            }
                            if (!StringUtils.isEmpty(user.getSex())) {
                                redisUser.setSex(user.getSex());
                            }
                            Map<String, Object> channelIds = redisUser.getChannelIds();

                            if (channelIds != null) {
                                channelIds.put(future.channel().id().asLongText(), ImTools.getHostAddress());
                                redisUser.setChannelIds(channelIds);
                            } else {
                                channelIds = new HashMap<>();
                                channelIds.put(future.channel().id().asLongText(), ImTools.getHostAddress());
                                redisUser.setChannelIds(channelIds);
                            }
                            ImLoginUtils.putSimpleUser(redisUser);
                        } else {
                            redisUser = user;
                            Map<String, Object> channelIds = redisUser.getChannelIds();

                            if (channelIds != null) {
                                channelIds.put(future.channel().id().asLongText(), ImTools.getHostAddress());
                                redisUser.setChannelIds(channelIds);
                            } else {
                                channelIds = new HashMap<>();
                                channelIds.put(future.channel().id().asLongText(), ImTools.getHostAddress());
                                redisUser.setChannelIds(channelIds);
                            }
                            ImLoginUtils.putSimpleUser(redisUser);
                        }

                        // 加入到ChannelHolders中
                        ChannelsHolder.addChannel(user, future.channel());
                        log.debug("注册用户信息到分布式缓存成功--- userid:"+userid+",username:"+username);
                        if (future.isSuccess()) {
                            String msg = "客户端" + username + "加入聊天室";
                            ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
                        }
                    }catch (Exception e){
                        log.error("",e);
                        future.channel().pipeline().fireExceptionCaught(e);
                    }finally {

                    }
                }
            });
            return; 
    }

    /**
     * 处理WebSocket请求
     * 
     * @param ctx
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        //log.debug("收到客户端发送的请求----" + frame.toString());
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            ctx.close();
            return;
        }
        // 没有使用WebSocketServerProtocolHandler，所以不会接收到PingWebSocketFrame。
        // if (frame instanceof PingWebSocketFrame) {
        // ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
        // return;
        // }
        if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(
                    String.format("%s frame types not supported", frame.getClass().getName()));
        }

        String request = ((TextWebSocketFrame) frame).text();

        // 回复心跳 空字符串或者长度为1的任意字符串代表是心跳检测消息
        if (!StringUtils.hasText(request) || request.length()==1) {
            //log.debug("收到客户端发送的数据：" + request);
            ctx.writeAndFlush(new TextWebSocketFrame(request));
            return;
        }
        try {
            log.debug("收到客户端发送的数据：" + request);
            this.handleMessage(ctx.channel(), request);
		} catch (Exception e) {
			log.error("",e);
		}
    }

    /**
     * 得到Http请求的Query String
     * 
     * @param req
     * @param name
     * @return
     */
    private static String getRequestParameter(FullHttpRequest req, String name) {
        QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
        Map<String, List<String>> parameters = decoder.parameters();
        Set<Entry<String, List<String>>> entrySet = parameters.entrySet();
        for (Entry<String, List<String>> entry : entrySet) {
            if (entry.getKey().equalsIgnoreCase(name)) {
                return entry.getValue().get(0);
            }
        }
        return null;
    }
    
    /**
     * 处理消息
     * @param channel
     * @param message
     */
    private void handleMessage(Channel channel, String message) {
        JSONObject json = null;
        try {
            json = JSONObject.parseObject(message);
            String channelId=channel.id().asLongText();
            SimpleUser user=ChannelsHolder.getSimpleUserByChannelId(channelId);
            if(user!=null) {
            	 json.put("sendUserid", user.getUserid());
            	 json.put("sendUsername", user.getUsername()); 
            } 
            json.put("channelId", channelId);
            json.putIfAbsent("msgId", SequenceFormat.parse("{date62:yyyyMMddHHmmssS}{rands:4}"));
            Date d=new Date();
            json.putIfAbsent("sendTime",d.getTime());
            String gloNo=LogUtils.getGloNo(false);
            json.put("gloNo",gloNo);
        } catch(JSONException e) {
            log.error("",e);
            return;
        }
        String action=json.getString("action");
        if(action==null) {
            log.warn("can not find action.");
            return;
        }
        if(Action.BIND_CHANNEL_TO_GROUPS.equals(action)) {
        	bindChannelToChannelGroups(json, channel);
        	return;
        }
        
        String msgType=json.getString("msgType");
        switch(msgType) {
            case MsgType.PRICHAT:
                //bindPrichatChannelToChannelGroup(json, channel);
                createPrichatChannelGroupId(json, channel);
                pushService.publishMessage(json);
                break;
            case MsgType.GROUP:
                //bindChannelToChannelGroup(json, channel);
                pushService.publishMessage(json);
                break;
            case MsgType.SYS:
                //bindChannelToChannelGroup(json, channel);
                pushService.publishMessage(json);
                break;
        }
       
    }
    
    /**
     * 绑定channel到ChannelGroup中
     * @param message
     * @param channel
     */
    private void bindChannelToChannelGroup(JSONObject message, Channel channel) {
        String groupId = message.getString("groupId");
        if(StringUtils.isEmpty(groupId)) {
        	return;
        }
        ChannelsHolder.addChannelToGroup(groupId, channel);
    }
    /**
     * 绑定channel到ChannelGroup中
     * @param message
     * @param channel
     */
    private void bindChannelToChannelGroups(JSONObject message, Channel channel) {
        JSONArray jsonArray=message.getJSONArray("groupIds"); 
        if(jsonArray==null || jsonArray.size()<=0) {
        	return;
        }
        for (Object groupId : jsonArray) {
        	ChannelsHolder.addChannelToGroup((String) groupId, channel);
		} 
    }
    /**
     * 绑定私聊channel到ChannelGroup中
     * @param message
     * @param channel
     */
    private void createPrichatChannelGroupId(JSONObject message, Channel channel) {
    	String groupId=message.getString("groupId");
    	if(!StringUtils.isEmpty(groupId)) {
    		return;
    	}
		String sendUserid=message.getString("sendUserid"); 
		String toUserid=message.getString("toUserid"); 
		List<String> userids=new ArrayList<>();
    	if(StringUtils.isEmpty(groupId)) { 
    		userids.add(sendUserid);
    		userids.add(toUserid);
    		Collections.sort(userids);
    		groupId=userids.get(0)+"_"+userids.get(1);
    		message.put("groupId", groupId);  
    	} 
    }

    
    /**
     * 客户端下线
     * @param ctx
     */
    private void clientOffline(ChannelHandlerContext ctx) {
        String channelId = ctx.channel().id().asLongText();
        ctx.close();
        ChannelsHolder.removeChannel(channelId);

    }
    /**
     * 用户上线时添加频道到内存中
     */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		super.channelActive(ctx);
		//ChannelsHolder.addChannel(ctx.channel());
	}

    /**
     * 获取WebSocket服务信息
     *
     * @param req
     * @return
     */
    private static String getWebSocketLocation(FullHttpRequest req) {
        String location = req.headers().get("Host") + "/imsg/m1";
        //log.debug(location);
        return "wss://" + location;
    }
}